Skip to content

πŸ› [v6] contributor-first: a relay in a disabled tier or with no matching work does not cover a lane - #17284

Merged
hivecommons-hive[bot] merged 2 commits into
hivecommons:v6from
Danathar:fix/v6-disabled-tier-no-work-not-cover
Nov 8, 2026
Merged

hivecommons-hive[bot] merged 2 commits into
hivecommons:v6from
Danathar:fix/v6-disabled-tier-no-work-not-cover

Conversation

@Danathar

@Danathar Danathar commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Summary

A contributor relay that the hub will not hand work to, or has no work for, still counted as covering its contributor-first lane, so the governor kept the resident agent's kicks withheld while nobody worked the lane. This is the same class of bug as #17233 (standby, quota hold) and #17268 (hourly/daily limit). With this change, RoleCoverage leaves out two more kinds of relay: one that is revoked or whose trust tier is in hub.disabled_tiers, and one that holds no task and has been answered no_matching_work (or repo_unmintable) for two minutes. The change is hub-side only. The relay is not touched.

What went wrong

  • Disabled tier / revoked. Selection refuses a relay in a disabled tier with tier_disabled, and a resume is refused for a disabled tier or a revoked profile. The socket stays up and heartbeats, so RoleCoverage counted the relay anyway.
  • No matching work. A relay whose every ready is answered no_matching_work is connected and idle. Seen live on 2026-11-08 on hive-wild-mole: the relay covered the scanner lane while selection answered no_matching_work every 30 seconds, so the resident scanner was withheld and nobody scanned.

Refs #17233, Refs #17268.

What changes

  • One tier gate, shared. The revoked and disabled-tier rules move out of resumeGateReason into tierAccessGateReason(tier). Its disabled-tier half, tierDisabled(tier), is what fresh selection now calls; selection used to have its own copy of the disabled_tiers loop. resumeGateReason gives the same reasons in the same order as before: revoked outranks everything, and a disabled tier is reported after the hub checks. Selection's log line, decision text and tier_disabled reply are unchanged, and selection still does not check revoked, which is refused at auth. RoleCoverage copies the tier out under c.mu and calls tierAccessGateReason after h.mu is released, the same way the rate check works.
  • An idle run on the connection. lastIdleReason already held the last task_unavailable reason. A new lastIdleReasonSince records when the current run of that reason began. no_matching_work and repo_unmintable count as one run, because the re-scan wrapper can rename one into the other with nothing changing.
    • Set by noteIdleReasonLocked, in the ready handler and in the yank/requeue reassignment path. Both already set lastIdleReason.
    • Cleared by clearIdleReasonLocked on every path that hands the relay work:
      • the selection commit, which covers issue, external, claim-preempt and operator-requeue assignments;
      • standby dispatch;
      • resume adoption on task_progress;
      • sendFixTask, the one place every fix-pr dispatch goes through.
  • The window is 2 minutes, measured from the first empty answer. An idle relay re-asks 30 s after each task_unavailable (TASK_UNAVAILABLE_RETRY_MS), so this is about five empty answers in a row. One empty answer between two tasks never reaches it. The window is a time, not an answer count, so a relay that asks less often is judged on how long it has been idle. A longer window is not needed to stop flapping: the governor samples coverage once per evaluation (300 s by default), and a lane that drops out of the report is still withheld for contributor_first_grace (10 min by default). A relay that finds work again inside the grace keeps the lane without any resident kick.
  • A busy relay still counts. That means a relay holding a primary task or any fix-pr task, as today.
  • Docs.
    • The RoleCoverage comment and the "Contributor-first lanes" section of src/docs/agent-configuration.md now list every exclusion in one place.
    • The governor's contributor-first header names the two new exclusions.
    • One drifted line citation in agent-state-inventory.md is re-pointed.

Not changed: governor gate logic, relay code, and how often the relay retries. Other idle reasons (no_selected_work, capability_mismatch) are left as they were. They are worth a look in a follow-up.

Files

  • src/pkg/dashboard/contribute_coverage.go: RoleCoverage, plus coverageNoWorkWindow and the idle-run helpers.
  • src/pkg/dashboard/contribute_select.go: tierAccessGateReason / tierDisabled, used by resumeGateReason and selectTaskPass. Also clears the idle run on assignment.
  • src/pkg/dashboard/contribute_ws.go: the lastIdleReasonSince field. Records the run in the ready and yank paths, and clears it on resume adoption.
  • src/pkg/dashboard/contribute_fixqueue.go, contribute_standby_dispatch.go: clear the idle run.
  • src/pkg/governor/contributor_first.go: header comment only.
  • src/docs/agent-configuration.md, src/docs/design/agent-state-inventory.md, changelog.d/fixed-disabled-tier-no-work-relay-not-cover.md.

Tests

New tests in contribute_coverage_test.go. The no-work tests drive real handleReady over a socket, so the reasons come from real selection.

Test On v6 tip With this PR
TestRoleCoverage_DisabledTierDoesNotCover FAIL pass
TestRoleCoverage_RevokedDoesNotCover FAIL pass
TestRoleCoverage_OneNoMatchingWorkAnswerStillCovers pass (guard) pass
TestRoleCoverage_NoMatchingWorkForWindowDoesNotCover FAIL pass
TestRoleCoverage_RepoUnmintableBehavesLikeNoMatchingWork FAIL pass
TestRoleCoverage_FixTaskDispatchEndsNoWorkRun FAIL pass
TestRoleCoverage_OtherIdleReasonEndsNoWorkRun new helper pass
TestTierAccessGateReason new helper pass
  • The disabled-tier test also re-enables the tier and checks that the same relay covers again.
  • The window test also hands the relay a task afterwards and checks that it covers while working the task and after the task ends.
  • The one-answer test is a guard against dropping cover too early, so it cannot fail on the tip, which never drops cover.

Also ran go test ./pkg/dashboard/... ./pkg/governor/..., go vet, the docs citation and link checks, and the api-reference citation check.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1

…ing work does not cover a lane

RoleCoverage counted a relay as covering its role when the hub would not
hand it work (revoked, or its trust tier in hub.disabled_tiers) and when
every ready it sent was answered no_matching_work. Both stay connected,
so the governor kept the resident agent's kicks withheld while nobody
worked the lane. Seen on hive-wild-mole on 2026-11-08: a relay covered
scanner while selection answered no_matching_work every 30 s. Same class
as hivecommons#17233 and hivecommons#17268.

- Factor revoked / disabled tier into tierAccessGateReason (disabled half:
  tierDisabled). resumeGateReason keeps its reasons and order; fresh
  selection's disabled_tiers loop now calls tierDisabled. RoleCoverage
  reads the tier under c.mu and evaluates with h.mu released.
- Record the idle run on the connection: lastIdleReasonSince beside
  lastIdleReason, set on task_unavailable in the ready and yank paths,
  no_matching_work and repo_unmintable folded into one run. Cleared on the
  selection commit, standby dispatch, resume adoption and sendFixTask.
- RoleCoverage skips a relay holding no task that has been answered
  no_matching_work / repo_unmintable for coverageNoWorkWindow (2 min from
  the first answer; the relay retries every 30 s and the governor still
  applies contributor_first_grace on top).
- Docs: RoleCoverage comment, agent-configuration.md coverage list,
  governor header, one drifted citation; changelog fragment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Nov 8, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Open PR overlap check

Base branch: v6

Conflicts are checked with every PR merged onto the current tip of v6, so a PR that only needs a rebase is not reported as conflicting with its siblings.

No conflicting open PRs or same-file overlaps were found.

@hivecommons-hive
hivecommons-hive Bot merged commit 0ec4fd6 into hivecommons:v6 Nov 8, 2026
69 of 70 checks passed
@hivecommons-hive

Copy link
Copy Markdown
Contributor

Hive status β€” updated in place; edits do not notify. Full history is in the Activity Monitor.

Evidence bundle `#1728426ffcec` sha256:b85839792fb0702226fabb7c01e21de8c507f6579f2b640633d1eb1199f96c01 β€” download from the dashboard or `hivectl review evidence #17284`.

@hivecommons-hive

Copy link
Copy Markdown
Contributor

merged by Hive trusted-author auto-merge: author @Danathar holds role owner and GitHub permission maintain on repo; CI green at 26ffcec.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read contribute_coverage.go, contribute_select.go, contribute_ws.go, contribute_fixqueue.go and contribute_standby_dispatch.go at the PR head (26ffcec) and checked every writer of the idle-run fields. Looks correct to me; the body's description matches the diff in every particular I checked.

  • Idle-run bookkeeping is closed. At the head there are exactly two setters (contribute_ws.go:2258 yank/requeue, :3778 handleReady) and four clearers (contribute_select.go:1275 selection commit, contribute_ws.go:4195 resume adoption, contribute_fixqueue.go:452 sendFixTask, contribute_standby_dispatch.go). No remaining raw lastIdleReason = assignment bypasses lastIdleReasonSince. Because clearIdleReasonLocked zeroes Since, the run after a task ends restarts from the first post-task empty answer, so a relay that just finished work is not dropped by a stale timestamp.
  • noWorkForWindowLocked is safe against partial state: a cleared reason ("") fails the idleRunKey(...) == no_matching_work test, so even an unexpected path that only cleared the string could not drop cover.
  • resumeGateReason ordering is preserved. Old: revoked β†’ hub-not-ready β†’ suspended β†’ disabled tier. New (contribute_select.go:377-398): tierAccessGateReason is computed up front but only an early return for resumeRefusedAccessRevoked; the disabled-tier value is returned after the hub checks. contributorTierRevoked is "revoked" (contribute_profiles.go:533), so the literal-to-constant swap is a no-op. tierDisabled folds in the old deps != nil && Config != nil guard.
  • selectTaskPass keeps the same log line, decision text and tier_disabled reply; only the loop moved.
  • Lock discipline matches the existing rate check: tier is copied out under c.mu, tierAccessGateReason runs after h.mu is released and takes no lock.

One trade-off worth a maintainer's eye, not a defect: once the queue is genuinely empty for the relay, the resident agent will now be kicked after coverageNoWorkWindow + contributor_first_grace and will usually find the same nothing. That is the intended cost per the changelog ("nobody worked the lane" is worse), just noting it is a behaviour change operators will see as extra resident kicks on quiet lanes.

β€” hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant